use alloc::boxed::Box;
use alloc::string::String;
use alloc::string::ToString;
use alloc::vec::Vec;
use alloc::{format, vec};
use cjson::Json;
use coap_lite::{
    CoapOption, CoapRequest, ContentFormat, MessageClass, MessageType, ObserveOption, Packet,
    RequestType,
};
use core::ffi::c_char;
use core::time::Duration;
use embed_std::thread::sleep_ms;
use embed_std::time::now;
use embed_std::{debugln, errorln, infoln};
use embed_std::{Error, Result};
use md5_rs::INPUT_BUFFER_LEN;
use std::net::{SocketAddr, UdpSocket};

const HEARTBEAT_INTERVAL: u32 = 15;
const PROTO_VER: u8 = 1; //json

pub struct LowEnergyClientOption {
    pub addrs: Vec<String>,
    pub product_code: String,
    pub device_uid: String,
    pub secret: String,
    pub handler: Option<Box<dyn HandleData>>,
}

impl LowEnergyClientOption {
    pub(crate) fn handle_data(&mut self, payload: &[u8], is_req: bool) {
        if let Some(ref mut handle) = self.handler {
            handle.handle(payload, is_req)
        }
    }
}

pub struct LowEnergyClient {
    opt: LowEnergyClientOption,
    addrs: Vec<SocketAddr>,
    sock: Option<UdpSocket>,
    client_id: String,
    pwd: String,
    req_msg_id: u16,
    last_hb_time: u64,
    token: u8,
    status: i32,
}

pub trait HandleData {
    fn handle(&mut self, payload: &[u8], is_req: bool);
}

impl LowEnergyClient {
    pub fn new(opt: LowEnergyClientOption) -> Self {
        Self {
            opt,
            addrs: vec![],
            sock: None,
            client_id: "".to_string(),
            pwd: "".to_string(),
            req_msg_id: 0,
            last_hb_time: 0,
            token: 1,
            status: 1, //sleeping
        }
    }

    pub fn init(&mut self) -> Result<()> {
        for addr in self.opt.addrs.iter() {
            self.addrs.push(
                addr.parse::<SocketAddr>()
                    .map_err(|e| Error::Other(e.to_string()))?,
            );
        }
        self.client_id = format!("{}@{}", self.opt.device_uid, self.opt.product_code);
        self.pwd = self.make_pwd();
        let sock = UdpSocket::bind("0.0.0.0:0").map_err(|e| Error::Other(e.to_string()))?;
        self.sock = Some(sock);

        self.subscribe(
            true,
            format!(
                "/mqtt/+/f/+/+/t/{}/{}/model/+/r/+",
                self.opt.product_code, self.opt.device_uid
            )
            .as_str(),
        )?;
        // self.subscribe(true, format!("/mqtt/+/f/{}/{}/t/+/+/model/+/a/+", self.opt.product_code, self.opt.device_uid).as_str())?;
        let _ = self.report(self.status);
        Ok(())
    }

    fn make_pwd(&self) -> String {
        let n = now().as_secs() + 3600 * 24 * 365;
        let plain_text = format!(
            "{}:{}:{}:{}",
            self.opt.product_code, self.opt.device_uid, self.opt.secret, n
        );
        let mut ctx = md5_rs::Context::new();
        let mut pos = 0usize;
        while pos < plain_text.len() {
            let len = if pos + INPUT_BUFFER_LEN > plain_text.len() {
                plain_text.len() - pos
            } else {
                INPUT_BUFFER_LEN
            };
            ctx.read(&plain_text.as_bytes()[pos..(pos + len)]);
            pos += len;
        }
        let digest = ctx.finish();
        let mut data = String::new();
        data.push_str("0$");
        for i in 0..16 {
            data.push_str(format!("{:02x}", digest[i]).as_str());
        }
        data.push_str(format!("${}", n).as_str());
        data
    }

    pub fn report(&mut self, status: i32) -> Result<()> {
        self.status = status;
        self.send_report(status)
    }

    pub fn handle(&mut self, to_in_ms: u32) -> Result<()> {
        self.check_inited()?;
        self.heartbeat()?;
        let mut req = None;
        {
            let sock = self.sock.as_mut().unwrap();
            let _ = sock.set_read_timeout(Some(Duration::from_millis(to_in_ms as u64)));
            let mut buf = [0; 2048];
            if let Ok((size, src)) = sock.recv_from(&mut buf) {
                if let Ok(packet) = Packet::from_bytes(&buf[..size]) {
                    debugln!("type {:?} : {:?} ", packet.header.get_type(), packet.header);
                    if packet.header.get_type() == MessageType::Confirmable {
                        let mut ack_packet = Packet::new();
                        ack_packet.header.code = MessageClass::Empty;
                        ack_packet.header.set_type(MessageType::Acknowledgement);
                        ack_packet.header.message_id = packet.header.message_id;
                        let _ = sock.send_to(ack_packet.to_bytes().unwrap().as_slice(), &src);
                    }
                    match packet.header.code {
                        MessageClass::Request(_r) => {
                            req = Some(packet);
                        }
                        MessageClass::Response(_r) => {
                            req = Some(packet);
                        }
                        _ => {}
                    }
                }
            }
        }
        if let Some(packet) = req {
            self.try_handle_topic_request(packet.payload.as_slice())?;
        }
        Ok(())
    }

    fn try_handle_topic_request(&mut self, payload: &[u8]) -> Result<()> {
        if payload.len() > 0 && payload[0] == 1 {
            //json
            let data = unsafe { core::str::from_utf8_unchecked(&payload[1..]) };
            let root = Json::parse(data)?;
            if let Some(extra) = root.get("__extra\0".as_ptr() as *const c_char) {
                if let Some(t) = extra.get("topic\0".as_ptr() as *const c_char) {
                    if let Some(topic) = t.as_str() {
                        if topic.find("/r/").is_some() {
                            self.opt.handle_data(&payload[1..], true);

                            let nt = topic.replace("/r/", "/a/");
                            debugln!("req {} resp {}", topic, nt);
                            self.send_topic_resp(nt.as_str())?;
                        }
                    }
                }
            }
        }
        Ok(())
    }

    fn check_inited(&self) -> Result<()> {
        if self.addrs.is_empty() {
            return Err(Error::Other("address is empty".to_string()));
        }

        if self.sock.is_none() {
            return Err(Error::Other("sock not inited".to_string()));
        }
        Ok(())
    }

    fn subscribe(&mut self, sub: bool, topic: &str) -> Result<()> {
        self.check_inited()?;
        let mut request = self.build_request(topic, RequestType::Get)?;
        request.message.set_token(vec![self.token]);
        let flag = if sub {
            ObserveOption::Register
        } else {
            ObserveOption::Deregister
        };
        request.set_observe_flag(flag);
        let sock = self.sock.as_mut().unwrap();
        let packet = request.message.to_bytes().unwrap();
        for addr in self.addrs.iter() {
            match sock.send_to(packet.as_slice(), addr) {
                Ok(_) => {
                    Self::pull_response(sock, self.req_msg_id, 2000)?;
                }
                Err(e) => {
                    return Err(Error::Other(e.to_string()));
                }
            }
        }
        debugln!("subscribe ok");
        Ok(())
    }

    fn pull_response(sock: &mut UdpSocket, seq: u16, to_in_ms: u32) -> Result<()> {
        let _ = sock.set_read_timeout(Some(Duration::from_millis(to_in_ms as u64)));
        let mut buf = [0; 2048];
        if let Ok((size, _)) = sock.recv_from(&mut buf) {
            if let Ok(packet) = Packet::from_bytes(&buf[..size]) {
                if packet.header.get_type() == MessageType::Acknowledgement
                    && packet.header.message_id == seq
                {
                    return Ok(());
                }
            }
        }
        Err(Error::Other("timeout".to_string()))
    }

    fn send_report(&mut self, status: i32) -> Result<()> {
        let topic = format!(
            "/mqtt/v2/dev/{}/{}/model/reports/props",
            self.opt.product_code, self.opt.device_uid
        );
        let mut request = self.build_request(topic.as_str(), RequestType::Put)?;
        request.message.payload = self.make_report_payload(status)?;
        self.send_to_servers(request.message)?;
        Ok(())
    }

    fn heartbeat(&mut self) -> Result<()> {
        let cur = now().as_secs();
        if cur < self.last_hb_time + (HEARTBEAT_INTERVAL as u64) / 2 {
            return Ok(());
        }
        self.send_report(self.status)?;
        self.last_hb_time = cur;
        Ok(())
    }

    fn make_report_payload(&mut self, status: i32) -> Result<Vec<u8>> {
        let mut data = Vec::new();
        data.push(PROTO_VER);
        let ts = now().as_secs();
        // 0: normal, 1:sleeping, 2:waking, 3: woke, 4: to wake
        data.extend(format!("{{\"heartbeat\":{{\"t\":{{\"v\":{},\"vl\":4,\"vt\":4}},\"status\":{{\"v\":\"{}\",\"vl\":1,\"vt\":13}} }} }}",
            ts, status).as_bytes());
        Ok(data)
    }

    fn send_topic_resp(&mut self, topic: &str) -> Result<()> {
        let topic = format!("/mqtt/{}", topic);
        let mut request = self.build_request(topic.as_str(), RequestType::Put)?;
        let mut data = Vec::new();
        data.push(PROTO_VER);
        data.extend(format!("{{\"rc\":0, \"rd\":\"ok\", \"data\": {{ }} }}").as_bytes());
        request.message.payload = data;
        self.send_to_servers(request.message)?;
        Ok(())
    }

    fn build_request(
        &mut self,
        topic: &str,
        method: RequestType,
    ) -> Result<CoapRequest<SocketAddr>> {
        let mut request: CoapRequest<SocketAddr> = CoapRequest::new();
        // self.pwd = self.make_pwd();
        request.set_method(method);
        request.set_path(topic);
        request.message.add_option(
            CoapOption::UriQuery,
            Vec::from(format!("c={}", self.client_id)),
        );
        request.message.add_option(
            CoapOption::UriQuery,
            Vec::from(format!("u={}", self.client_id)),
        );
        request
            .message
            .add_option(CoapOption::UriQuery, Vec::from(format!("p={}", self.pwd)));
        request.message.set_token(vec![self.token]);
        request
            .message
            .set_content_format(ContentFormat::ApplicationOctetStream);
        self.req_msg_id = self.req_msg_id.wrapping_add(1);
        request.message.header.message_id = self.req_msg_id;
        Ok(request)
    }

    fn send_to_servers(&mut self, packet: Packet) -> Result<()> {
        let sock = self.sock.as_mut().unwrap();
        let data = packet.to_bytes().unwrap();
        for addr in self.addrs.iter() {
            for _ in 0..3 {
                match sock.send_to(data.as_slice(), addr) {
                    Ok(_) => {
                        break;
                    }
                    Err(e) => {
                        errorln!("sent heartbeat to {:?} failed: {}", addr, e);
                    }
                }
                sleep_ms(50);
            }
        }
        self.last_hb_time = now().as_secs();
        Ok(())
    }
}

impl Drop for LowEnergyClient {
    fn drop(&mut self) {
        infoln!("drop LowEnergyClient");
    }
}
